// lex.cpp
//
// Copyright (C) 2008  Maksim Sipos <msipos@mailc.net>
//
// This file is a part of the Ripe language project.
//
// Ripe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "util/standard.hpp"
#include <cstdio>
#include <cstring>
#include "util/lex.hpp"
#include "util/re.hpp"
#include "util/utf8.hpp"

namespace Ripe {

Lexer::Lexer(uint32_t environments)
{
  envs = environments;
  nfas = rnew NFASplitNode*[envs];

  for (uint32_t i = 0; i < envs; i++){
    nfas[i] = rnew NFASplitNode;
  }

  // Starting environment
  stack.append(0);
}

void Lexer::add_rule(int env, int to_env, const char* pattern, int id)
{
  LexerRule rule = {id, to_env};
  rules.append(rule);

  // The result of re_match is an index into the LexerRule array.
  NFANode* regexp = re_compile(pattern, rules.size - 1);
  nfas[env]->children.append(regexp);
}

int Lexer::read_token()
{
  int id;
  do {
    id = read_raw_token();
  }  while (id == ID_CONSUME);
  return id;
}

int Lexer::read_raw_token()
{
  // Current environment
  int env = stack.last();

  // Returned match variables
  int match_id, match_len;

  // If the amount of leftover data is less than (maximum token size + 4),
  // then force rewinding the buffer.
  if (buf_len - idx < R_LEXER_TOKSIZE + 4){
    rewind_buf();

    // If buf_len, after rewinding, is still 0, then we are at EOF.
    if (buf_len == 0) return ID_EOF;
  };

  if (re_match(buf + idx, nfas[env], true, &match_id, &match_len)){
    return matched(match_id, match_len);
  } else {
    throw ErrorLexerCantMatch();
  }
}

void Lexer::set_input(std::istream* i)
{
  buf_len = 0;
  idx = 0;
  input = i;
  // Make sure the buffer is null terminated
  buf[buf_len] = 0;
}

void Lexer::rewind_buf(void)
{
  uint32_t unread = buf_len - idx;   // Length of the unprocessed buffer.
  uint32_t empty = R_LEXER_BUFSIZE - unread; // Empty space in the buffer.

  // Move unprocessed buffer space to the top of the buffer.
  std::memmove(buf, buf + idx, unread);
  idx = 0;

  // Fill as much as possible in the remaining space.
  input->read(buf + unread, empty);
  buf_len = unread + input->gcount();

  buf[buf_len] = 0;
}

int Lexer::matched(int matched_id, int matched_len) throw (ErrorBigToken)
{
  LexerRule rule = rules[matched_id];

  // Deal with environment rules
  switch (rule.to_env){
    case ENV_POP:
      stack.pop();
      break;
    case ENV_STAY:
      break;
    default:
      stack.append(rule.to_env);
  }

  if (matched_len > R_LEXER_TOKSIZE)
    throw ErrorBigToken();

  // Copy the token to the token buffer
  std::memcpy(token, buf + idx, matched_len);
  token[matched_len] = 0;
  idx += matched_len;

  return rule.id;
}

}
